package tazadum.razzmatazz.component;

import java.nio.FloatBuffer;
import java.util.Random;

import javax.media.opengl.GL;
import javax.media.opengl.GL2;

import tazadum.engine.math.Vector;
import tazadum.engine.math.Vectors;
import tazadum.engine.math.curve.BSplineCurve;
import tazadum.engine.render.Renderable;
import tazadum.engine.util.HeightMap;
import tazadum.util.BufferUtil;

public class SplineWormStrip implements Renderable {
	private static final int LENGTH = 40;
	private static final int SEGMENTS = 400;
	private static final float SPEED = 8;
	private static final float HEIGHT = 0.25f;

	private final Vector up = Vector.Y;
	private final BSplineCurve curve = new BSplineCurve();
	private final FloatBuffer vertices;
	private final HeightMap heightMap;
	private final Random random;
	private final int renderOffset;

	public SplineWormStrip(final GL2 gl, final Vector from, final Vector to, final HeightMap heightMap,
			final Random random) {
		this.heightMap = heightMap;
		this.random = random;

		curve.add(from);
		addPoints(from, to, 1);
		curve.add(to);

		vertices = calculateVertices(SEGMENTS, heightMap);
		renderOffset = random.nextInt(SEGMENTS);
	}

	private void addPoints(final Vector from, final Vector to, final int iter) {
		if (iter > 6) {
			return;
		}
		final Vector split = Vectors.mid(to, from);
		final Vector normal = Vectors.cross(split, up).normalize();
		final float length = Vectors.distance(from, to);
		final float displacement = (length * 0.25f * (random.nextBoolean() ? -1 : 1));

		split.x += normal.x * displacement;
		split.y += normal.y * displacement;
		split.z += normal.z * displacement;

		addPoints(from, split, iter + 1);
		curve.add(split);
		addPoints(split, to, iter + 1);
	}

	private FloatBuffer calculateVertices(final int segments, final HeightMap heightMap) {
		final FloatBuffer vertices = BufferUtil.newFloatBuffer(2 * 3 * segments);

		Vector previous = curve.calculate(0);
		for (int i = 0; i < segments; i++) {
			final float alpha = (i + 1) / (float) (SEGMENTS + 1);
			final float t = alpha * curve.nodeCount();

			final Vector current = curve.calculate(t);
			final Vector normal = Vectors.cross(up, Vectors.sub(current, previous));

			vertices.put(current.x);
			vertices.put(heightMap.at(current.x, current.z) + HEIGHT);
			vertices.put(current.z);
			final float px = current.x + normal.x * 0.5f;
			final float pz = current.z + normal.z * 0.5f;
			vertices.put(px);
			vertices.put(heightMap.at(px, pz) + HEIGHT);
			vertices.put(pz);

			previous = current;
		}

		vertices.rewind();
		return vertices;
	}

	@Override
	public void render(final GL2 gl, final float time) {
		final int offset = (int) (time * SEGMENTS / SPEED + renderOffset) % SEGMENTS;
		final int slice = Math.min(LENGTH, SEGMENTS - offset);

		final float[] dst = new float[6];
		vertices.position(offset * 2 * 3);
		for (int i = offset; i < offset + slice; i++) {
			vertices.get(dst, 0, 6);

			final float y1 = heightMap.at(dst[0], dst[2]);
			vertices.put(i * 6 + 1, y1 + HEIGHT);
			final float y2 = heightMap.at(dst[3], dst[5]);
			vertices.put(i * 6 + 4, y2 + HEIGHT);
		}

		vertices.position(offset * 2 * 3);
		gl.glEnableClientState(GL2.GL_VERTEX_ARRAY);
		gl.glVertexPointer(3, GL2.GL_FLOAT, 0, vertices);
		gl.glDrawArrays(GL.GL_TRIANGLE_STRIP, 0, slice * 2);
	}
}
